package top.cardone.context.util;

import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import top.cardone.cache.Cache;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.core.util.func.Func0;
import top.cardone.core.util.func.Func1;
import top.cardone.core.util.func.Func2;
import top.cardone.core.util.func.Func3;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yao hai tao
 * @date 2016/4/20
 */
@Log4j2
public class MapUtils extends org.apache.commons.collections.MapUtils {
    public static <V> Map<String, V> newMap(Map<String, V> map, Func1<Map<String, V>, Map<String, V>> func) {
        if (isEmpty(map) || (func == null)) {
            return map;
        }

        return func.func(map);
    }

    /**
     * 转换集合
     *
     * @param <V>       类型
     * @param map       原集合
     * @param mapperMap 配置
     * @return 新集合
     */
    public static <V> Map<String, V> newHashMap(Map<String, V> map, Map<String, Object> mapperMap) {
        if (isEmpty(mapperMap)) {
            return map;
        }

        if (map == null) {
            map = Maps.newHashMap();
        }

        HashMap<String, V> newMap = Maps.newHashMap();

        for (Map.Entry<String, Object> mapperEntry : mapperMap.entrySet()) {
            if (mapperEntry.getValue() == null) {
                newMap.put(mapperEntry.getKey(), null);

                continue;
            }

            if (mapperEntry.getValue() instanceof String) {
                if (map.containsKey(mapperEntry.getValue())) {
                    newMap.put(mapperEntry.getKey(), map.get(mapperEntry.getValue()));
                }

                continue;
            }

            if (mapperEntry.getValue() instanceof Map) {
                Map<String, Object> itemMapper = (Map<String, Object>) mapperEntry.getValue();

                for (Map.Entry<String, Object> itemMapperEntry : itemMapper.entrySet()) {
                    Object func = ApplicationContextHolder.getBean(itemMapperEntry.getKey());

                    if (func == null) {
                        continue;
                    }

                    if (func instanceof Func3 && itemMapperEntry.getValue() instanceof Map) {
                        newMap.put(mapperEntry.getKey(), (V) ((Func3) func).func(map, itemMapperEntry.getValue(), mapperEntry.getKey()));
                    } else if (func instanceof Func2) {
                        newMap.put(mapperEntry.getKey(), (V) ((Func2) func).func(map, itemMapperEntry.getValue()));
                    } else if (func instanceof Func1) {
                        if (itemMapperEntry.getValue() instanceof String) {
                            newMap.put(mapperEntry.getKey(), (V) ((Func1) func).func(map.get(itemMapperEntry.getValue())));
                        } else {
                            newMap.put(mapperEntry.getKey(), (V) ((Func1) func).func(itemMapperEntry.getValue()));
                        }
                    } else if (func instanceof Func0) {
                        newMap.put(mapperEntry.getKey(), (V) ((Func0) func).func());
                    }
                }

                continue;
            }

            newMap.put(mapperEntry.getKey(), (V) mapperEntry.getValue());
        }

        return newMap;
    }

    public static <V> Map<String, V> newHashMap(Map<String, V> map, String resourcePath, boolean cache) {
        return newHashMap(map, newMap(resourcePath, cache));
    }

    public static Map toMap(Object obj) {
        return toMap(obj, "objs", "obj");
    }

    public static Map toMap(Object obj, String objectKey) {
        return toMap(obj, "objs", objectKey);
    }

    public static Map toMap(Object obj, String objectsKey, String objectKey) {
        Map<Object, Object> newMap;

        if (obj instanceof LinkedCaseInsensitiveMap) {
            newMap = new LinkedCaseInsensitiveMap();
        } else {
            newMap = Maps.newHashMap();
        }

        if (obj == null) {
            return newMap;
        }

        if (obj instanceof Map) {
            Map putAllMap = (Map) obj;

            if (CollectionUtils.isEmpty(putAllMap)) {
                return newMap;
            }

            newMap.putAll(putAllMap);

            return newMap;
        }

        if (obj instanceof Collection || ObjectUtils.isArray(obj)) {
            newMap.put(objectsKey, obj);

            return newMap;
        }

        if (obj.getClass().isPrimitive() ||
                obj.getClass().equals(String.class) ||
                obj.getClass().equals(Integer.class) ||
                obj.getClass().equals(Byte.class) ||
                obj.getClass().equals(Long.class) ||
                obj.getClass().equals(Double.class) ||
                obj.getClass().equals(Float.class) ||
                obj.getClass().equals(Character.class) ||
                obj.getClass().equals(Short.class) ||
                obj.getClass().equals(BigDecimal.class) ||
                obj.getClass().equals(BigInteger.class) ||
                obj.getClass().equals(Boolean.class) ||
                obj.getClass().equals(Date.class)) {
            newMap.put(org.apache.commons.lang3.StringUtils.defaultIfBlank(objectKey, "obj"), obj);

            return newMap;
        }

        ReflectionUtils.doWithFields(obj.getClass(), field -> {
            Object value = FieldUtils.readField(field, obj, true);

            newMap.put(field.getName(), value);
        }, field -> !(Modifier.isStatic(field.getModifiers())));

        return newMap;
    }

    public static <K, V> Map<K, V> newMap(Resource resource) {
        if (!resource.exists()) {
            return Maps.newHashMap();
        }

        try {
            String json = FileUtils.readFileToString(resource.getFile(), Charsets.UTF_8);

            return ApplicationContextHolder.getBean(Gson.class).fromJson(json, new TypeToken<Map<K, V>>() {
            }.getType());
        } catch (IOException e) {
            log.error(e.getMessage(), e);

            return Maps.newHashMap();
        }
    }

    public static <K, V> Map<K, V> newMap(String resourcePath, boolean cache) {
        if (cache) {
            return ApplicationContextHolder.getBean(Cache.class).get("table", 1, resourcePath, () -> {
                Resource resource = ApplicationContextHolder.getResource(resourcePath);

                return newMap(resource);
            });
        }

        Resource resource = ApplicationContextHolder.getResource(resourcePath);

        return newMap(resource);
    }

    public static <K, V> Map<K, V> newMap(String resourcePath) {
        return newMap(resourcePath, true);
    }
}